﻿using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace IPCChannel
{
    public delegate void MessageReceivedEventHandler(IPCMessage message);

    public abstract class TCPClient : IIPCClient
    {
        private TcpListener listener;
        private string ip;
        private int port;
        private Thread listenThread;
        private bool disposed = false;
        private bool listening = false;

        public event MessageReceivedEventHandler MessageReceived;
        
        protected TCPClient(string ip, int port)
        {
            this.ip = ip;
            this.port = port;

            IPAddress addr = IPAddress.Parse(ip);
            listener = new TcpListener(addr, port);

            listenThread = new Thread(RunListen) { IsBackground = true };
        }

        public void StartListening()
        {
            listening = true;
            listenThread.Start();
        }

        public void StopListening()
        {
            listening = false;
        }

        public void Send(string recvIp, int recvPort, IPCMessage message)
        {
            message.SenderIp = GetIp();
            message.SenderPort = GetPort();

            using (TcpClient client = new TcpClient(recvIp, recvPort))
            {
                Byte[] data = System.Text.Encoding.ASCII.GetBytes("<" + message.GetType() + ">" + message.Serialize());

                using (NetworkStream stream = client.GetStream())
                {
                    stream.Write(data, 0, data.Length);
                }
            }
        }

        public string GetIp()
        {
            return ip;
        }

        public int GetPort()
        {
            return port;
        }

        public Encoding Encoding()
        {
            return new UTF8Encoding();
        }

        private void RunListen(object input)
        {
            try
            {
                listener.Start();

                // Buffer for reading data
                Byte[] bytes = new Byte[2048];
                String data = null;

                while (listening)
                {
                    // Perform a blocking call to accept requests. 
                    using (TcpClient client = listener.AcceptTcpClient())
                    {
                        data = null;

                        using (NetworkStream stream = client.GetStream())
                        {
                            int i;
                            while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                            {
                                // Translate data bytes to a ASCII string.
                                data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                            }
                        }
                    }
                    OnMessageReceived(data);
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                listener.Stop();
            }
        }

        protected virtual void OnMessageReceived(string message)
        {
            if (MessageReceived != null)
            {
                string jsonHeader = message.Split('>').First().Replace("<", "");
                string jsonObj = message.Split('>').Last();
                IPCMessage msgObj = MessageDeserialization(jsonHeader, jsonObj);

                MessageReceived(msgObj);
            } 
        }

        protected abstract IPCMessage MessageDeserialization(string typeHeader, string jsonMessag);

        #region IDisposable

        ~TCPClient()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (listening)
                        StopListening();
                }

                // Release unmanaged resources here
            }
            disposed = true;

            //base.Dispose(disposing);
        }

        #endregion IDisposable
    }
}
